var sys = require('sys');

function JsonRpc(){
    this._map = {};
    this._counter = 0;
}

JsonRpc.prototype.registerServer = function(server){
    this._server = server;
    return this;
};

JsonRpc.prototype.registerTransport = function(transport){
    this._transport = transport;
    var dispatcher = this;
    transport.addListener('data', function(data){
        dispatcher._dispatch(JSON.decode(data));
    });
    transport.addListener('close', function(){
        var hasOpenedRequests = false;
        for (var prop in this._map) {
            if (this._map.hasOwnProperty(prop)) {
                hasOpenedRequests = true;
                break;
            }
        }
        if (hasOpenedRequests) {
            throw new Error('Opened requests: ' + sys.inspect(this._map, false, null));
        }
    });
    return this;
};

JsonRpc.prototype._dispatch = function(data){
    if (data.hasOwnProperty('method')) {
        this._dispatchRequest(data);
    } else if (data.hasOwnProperty('result')) {
        this._dispatchResponse(data);
    } else {
        this._transport.close();
    }
};

JsonRpc.prototype._dispatchRequest = function(data){
    var
        params = data.params || [],
        id = data.id || undefined,
        result = null,
        error = null;

    if (null === data.id) {
        if ('Array') { // TODO: check Array type
            params.unshift(data.method);
            return server.emit.apply(server, params);
        } else {
            return server.emit(data.method, params);
        }
    }
    try {
        if ('Array') { // TODO: check Array type
            result = server[data.method].apply(server, params);
        } else {
            result = server[data.method](params);
        }
    } catch(e) {
        error = e.getMessage();
    }
    this.send({
        result: result || null,
        error: error,
        id: data.id
    });
};

JsonRpc.prototype._dispatchResponse = function(data){
    if (data.id && this._map.hasOwnProperty(data.id)) {
        this._map[data.id][1].emit(this._map[data.id][0], data.result);
        delete this._map[data.id];
    }
};

JsonRpc.prototype.call = function(client, method, params){
    var data = {
        method: method,
        params: params,
        id: ++ this._counter
    };
    this._map[data.id] = [method, client];
    this.send(data);
};

JsonRpc.prototype.send = function(data){
    this._transport.write(JSON.stringify(data));

};

function Client(){}
sys.inherits(Client, require('events').EventEmitter);

Client.prototype.setDispatcher = function(dispatcher){
    this._jsonrpc = dispatcher;
    return this;
};

Client.prototype.call = function(method, params){
    if (arguments.length < 3) {
        this._jsonrpc.call(this, method, params);
    } else {
        var params = [];
        for (var i = 1, l = arguments.length; i < l; ++ i) {
            params.push(arguments[i]);
        }
        this._jsonrpc.call.apply(this, [method, params]);
    }
};

exports.JsonRpc = JsonRpc;
exports.Client = Client;
